/* A COMPLETER - Pef, 7 avril
	Di-Costanzo Jonathan
	Fauché Pierre-Élie
	3TC 3
*/

#include <stdlib.h>
#include <string.h>

#include "p2p_common.h"
#include "p2p_addr.h"
#include "p2p_msg.h"
#include "p2p_file.h"
#include "p2p_research.h"


/***************************************************************************
Création (allocation) d'un p2p_search
***************************************************************************/
p2p_search p2p_search_create(){
	p2p_search retour;
	
	retour = (p2p_search)malloc(sizeof(struct p2p_search_struct));
	
	retour->length=0;
	retour->recherches=NULL;
	
	return retour;
}

/***************************************************************************
Suppression de la liste
***************************************************************************/
void p2p_search_delete(p2p_search liste){
	free(liste->recherches);
	free(liste);
}

/***************************************************************************
Ajout d'un élément à la liste
***************************************************************************/
void p2p_search_add(p2p_search liste, const unsigned char version, 
		const p2p_addr src, const unsigned long int req, const char *nom_fichier){
	
	liste->recherches=(p2p_search_item)realloc(liste->recherches, 
		(liste->length+1)*sizeof(struct p2p_search_item_struct));
	
	liste->recherches[liste->length].version=version;
	strcpy(liste->recherches[liste->length].initiator, p2p_addr_get_str(src));
	liste->recherches[liste->length].req_id=req;
	strcpy(liste->recherches[liste->length].name, nom_fichier);
	liste->recherches[liste->length].result=0;
	
	liste->length++;
}

/***************************************************************************
Comparaison d'un élément à ceux contenus dans la liste
Retourne 0 si l'élément n'a pas été trouvé.
***************************************************************************/
int p2p_search_compare(p2p_search liste, const unsigned char version,const p2p_addr src, const unsigned long int req, const char *name){
	int i;
	
	for (i=0; i<liste->length; i++) {
		if ( ( liste->recherches[i].req_id==req ) 
			&& ( liste->recherches[i].version==version ) 
			&& ( strcmp(liste->recherches[i].initiator, p2p_addr_get_str(src))==0 ) 
			&& ( strcmp(liste->recherches[i].name, name)==0 ) ) {
			// l'élément a été trouvé
			return 1;
		}
	} 
	return 0;
}

/***************************************************************************
On incrémente le nombre de résultats d'une recherche
***************************************************************************/
void p2p_search_one_more_result(p2p_search liste, const unsigned long int req){
	liste->recherches[req].result++;
}

/* utilisation de p2p_reply */

/***************************************************************************
Création (allocation) d'un p2p_reply
***************************************************************************/
p2p_reply p2p_reply_create() {
	p2p_reply retour;
	
	retour = (p2p_reply)malloc(sizeof(struct p2p_reply_struct));
	
	retour->length=0;
	retour->resultats=NULL;
	
	return retour;
}

/***************************************************************************
Suppression de la liste des réponses
***************************************************************************/
void p2p_reply_delete(p2p_reply liste) {
	free(liste->resultats);
	free(liste);	
}

/***************************************************************************
Ajout d'un élément à la liste des réponses
***************************************************************************/
void p2p_reply_add(p2p_reply liste, const p2p_addr src, const unsigned long int req, 
														const char *nom_fichier, const unsigned long int taille) {
	
	liste->resultats=(p2p_reply_item)realloc(liste->resultats, 
		(liste->length+1)*sizeof(struct p2p_reply_item_struct));
	
	strcpy(liste->resultats[liste->length].noeud, p2p_addr_get_str(src));
	liste->resultats[liste->length].req_id = req;
	strcpy(liste->resultats[liste->length].name, nom_fichier);
	liste->resultats[liste->length].size   = taille;
	
	liste->length++;
}

/***************************************************************************
Comparaison d'un élément à ceux contenus dans la liste
Retourne 0 si l'élément n'a pas été trouvé.
***************************************************************************/
int p2p_reply_compare(p2p_reply liste, const p2p_addr src, const unsigned long int req, 
															const char *nom_fichier, const unsigned long int taille) {
	int i;
	
	for (i=0; i<liste->length; i++) {
		if ( (liste->resultats[i].req_id==req) 
			&& (strcmp(liste->resultats[i].noeud, p2p_addr_get_str(src))==0)
			&& (strcmp(liste->resultats[i].name, nom_fichier)==0) 
			&& (liste->resultats[i].size==taille) ) {
			// l'élément a été trouvé
			return 1;
		}
	} 
	return 0;
}

/***************************************************************************
Récupération d'un pointeur vers la 
structure p2p_reply_item_struct répondant aux critères demandés
Renvoie NULL si pas trouvée
***************************************************************************/
p2p_reply_item p2p_search_get(server_params *sp, unsigned long int search, int result) {
	p2p_reply_item retour=NULL;
	int i=0,j=0;
	
	while ( (i<sp->p2pResultats->length) && (retour==NULL) && (j<=result) ) {
		if (sp->p2pResultats->resultats[i].req_id==search) {
			if (j==result) {
				retour=&(sp->p2pResultats->resultats[i]);
			} else j++;
		}
		i++;
	}
	
	return retour;
}

/***************************************************************************
Réception d'un message P2P_MSG_SEARCH               MODIFIÉ, VERSION 2
***************************************************************************/
int p2p_search_received(server_params *sp, p2p_msg search_msg) {
	
	//Récupération des données du message SEARCH
	unsigned char version = p2p_msg_get_version(search_msg);
	p2p_addr initiator = p2p_addr_create();
	memcpy(initiator, p2p_get_payload(search_msg), P2P_ADDR_SIZE);
	unsigned long int req_id, taille_fichier;
	memcpy(&req_id, &(p2p_get_payload(search_msg)[P2P_ADDR_SIZE]), P2P_INT_SIZE);
	req_id=ntohl(req_id);
	char file_name[p2p_msg_get_length(search_msg)-P2P_ADDR_SIZE-P2P_INT_SIZE];
	memcpy(file_name, &(p2p_get_payload(search_msg)[P2P_ADDR_SIZE+P2P_INT_SIZE]), 
		p2p_msg_get_length(search_msg)-P2P_ADDR_SIZE-P2P_INT_SIZE);
	
	//on vérifie si la recherche a déjà été vue ou faite
	if ( (p2p_search_compare(sp->p2pSearchFaits, version, initiator, req_id, file_name)==0) 
		&& (p2p_search_compare(sp->p2pSearchVus, version, initiator, req_id, file_name)==0) ) {
		
		//l'élément n'a pas été vu ni fait, on le rajoute aux déjàs vus
		p2p_search_add(sp->p2pSearchVus, version, initiator, req_id, file_name);
		
		//on rebroadcast le message
		if (p2p_udp_msg_rebroadcast(sp, search_msg) !=P2P_OK )
			return P2P_ERROR;
			
		if ( version == 1 ) {
			//on regarde si on a le fichier
			//unsigned long int taille_fichier;
			
			if (p2p_file_is_available(sp, file_name, &taille_fichier)==P2P_OK) {
				//on a le fichier, on construit le message
				p2p_msg reply_msg = p2p_msg_create();
				p2p_msg_init(reply_msg, P2P_MSG_REPLY, P2P_MSG_TTL_ONE_HOP, sp->p2pMyId, initiator);
				p2p_msg_set_version(reply_msg, version);
				
				//payload du reply
				unsigned char reply_payload[2*P2P_INT_SIZE];
				req_id=htonl(req_id);
				memcpy(reply_payload,&req_id, P2P_INT_SIZE);
				taille_fichier=htonl(taille_fichier);
				memcpy(&reply_payload[P2P_INT_SIZE],&taille_fichier, P2P_INT_SIZE);
				p2p_msg_init_payload(reply_msg, 2*P2P_INT_SIZE, reply_payload);
			
				//envoi du message par udp
				p2p_udp_msg_send(sp, reply_msg);
			
				p2p_msg_delete(reply_msg);
			}
		} else if ( version==P2P_VERSION ) {
			//on récupère les fichiers correspondants
      p2p_files liste_fic = p2p_files_available(sp, file_name, version);
                
      //on envoie un message par fichier correspondant au mot clé
      p2p_msg reply_msg = p2p_msg_create();
      p2p_msg_init(reply_msg, P2P_MSG_REPLY, P2P_MSG_TTL_ONE_HOP, sp->p2pMyId, initiator);
                
      //payload du reply
      unsigned char *reply_payload=(unsigned char*)malloc(2*P2P_INT_SIZE*sizeof(unsigned char));
      req_id=htonl(req_id);
      memcpy(reply_payload,&req_id, P2P_INT_SIZE);
                
      int i;
      for (i=0; i<p2p_files_length(liste_fic); i++) {
        //on réalloue le payload
        reply_payload=(unsigned char*)realloc(reply_payload, 
            (2*P2P_INT_SIZE+strlen(p2p_files_get_name(liste_fic, i))+1)*sizeof(unsigned char));
        //taille du fichier
        taille_fichier=htonl(p2p_files_get_size(liste_fic, i));
        memcpy(&reply_payload[P2P_INT_SIZE], &taille_fichier, P2P_INT_SIZE);
        //nom du fichier
        memcpy(&reply_payload[P2P_INT_SIZE*2], p2p_files_get_name(liste_fic, i), 
            strlen(p2p_files_get_name(liste_fic, i))+1);
        //on met le payload dans le message
        p2p_msg_init_payload(reply_msg, 2*P2P_INT_SIZE+strlen(p2p_files_get_name(liste_fic, i))+1, reply_payload);
        
        //envoi du message par udp
        p2p_udp_msg_send(sp, reply_msg);
      }
      p2p_msg_delete(reply_msg);
      p2p_files_delete(liste_fic);
      free(reply_payload);
		} else {
			//afficher kke chose si c'est une version inconnue
    } 
	}
	
	p2p_addr_delete(initiator);
	return P2P_OK;
}

/***************************************************************************
Envoi d'un message P2P_MSG_SEARCH
On envoie deux messages search, un avec la version 1 simplifié,
l'autre avec notre version, complet
***************************************************************************/
int p2p_search_send(server_params *sp, char *file_name) {
	//VERBOSE(sp,CLIENT,"-->on rentre dans la fonction p2p_search_send\n");
	p2p_msg search_msg = p2p_msg_create();
	if (p2p_msg_init(search_msg, P2P_MSG_SEARCH, P2P_MSG_TTL_MAX, 
		sp->p2pMyId, p2p_addr_broadcast()) !=P2P_OK) {
		p2p_msg_delete(search_msg);
		return P2P_ERROR;
	}
	
	//structure pour le payload du message
	unsigned char search_payload[P2P_ADDR_SIZE+P2P_INT_SIZE+strlen(file_name)+1];
	memcpy(search_payload, sp->p2pMyId, P2P_ADDR_SIZE);
	unsigned long int req_id=htonl(sp->p2pSearchFaits->length);
	memcpy(&search_payload[P2P_ADDR_SIZE], &req_id, P2P_INT_SIZE);
	memcpy(&search_payload[P2P_ADDR_SIZE+P2P_INT_SIZE], file_name, strlen(file_name)+1);
	
	//on met le payload dans le message
	p2p_msg_init_payload(search_msg, P2P_ADDR_SIZE+P2P_INT_SIZE+strlen(file_name)+1, search_payload);
	
	//on ajoute la recherche dans notre liste de recherches faites
	p2p_search_add(sp->p2pSearchFaits, P2P_VERSION, sp->p2pMyId, sp->p2pSearchFaits->length, file_name);
	
	//on envoie le message à nos 2 voisins
	if ( (p2p_udp_msg_sendto(sp, search_msg, sp->neighbors.left)!=P2P_OK) ||
		(p2p_udp_msg_sendto(sp, search_msg, sp->neighbors.right)!=P2P_OK) ) {
		//faudra bien faire quelque chose...un jour...ou pas.
	}
	
	//envoi du message en version de base
	p2p_msg_set_version(search_msg, 1);
	//on envoie le message à nos 2 voisins
	if ( (p2p_udp_msg_sendto(sp, search_msg, sp->neighbors.left)!=P2P_OK) ||
		(p2p_udp_msg_sendto(sp, search_msg, sp->neighbors.right)!=P2P_OK) ) {
		//faudra bien faire quelque chose...un jour...ou pas.
	}
	
	p2p_msg_delete(search_msg);
	
	return P2P_OK;
}

/***************************************************************************OK
Réception d'un message P2P_MSG_REPLY
***************************************************************************/
int p2p_reply_received(server_params *sp, p2p_msg msg) {
	
	//on stocke la version, le req_id et le file_size
	unsigned char version = p2p_msg_get_version(msg);
	unsigned long int req_id = p2p_msg_get_req_id_reply(msg);
	unsigned long int file_size = p2p_msg_get_file_size_reply(msg);
		
	//récupération du nom du fichier en question
	char *nom_fic;
	if ( version != P2P_VERSION ) {
		nom_fic=sp->p2pSearchFaits->recherches[req_id].name;
	} else {
		nom_fic=(char *)&(p2p_get_payload(msg)[2*P2P_INT_SIZE]);
	}
	
	//on met de nouveaux résultats seulement s'ils n'existent pas déjà
	if (p2p_reply_compare(sp->p2pResultats, p2p_msg_get_src(msg), req_id, nom_fic, file_size)==0){
	
		//on incrémente le nombre de résultats d'une recheche
		p2p_search_one_more_result(sp->p2pSearchFaits, req_id);

		//on ajoute le résultat dans la liste
		p2p_reply_add(sp->p2pResultats, p2p_msg_get_src(msg), req_id, nom_fic, file_size);
	}

	return P2P_OK;
}

/***************************************************************************
Affichage des recherches effectuées
***************************************************************************/
int p2p_list_search(server_params *sp) {
	int i;
	
	VERBOSE(sp,GUI,"\n");
	VERBOSE(sp,GUI,"SEARCH::CLEAR\n");
	
	if (sp->p2pSearchFaits->length==0) {
		VERBOSE(sp,CLIENT,"Aucune recherche faite\n\n");
		return P2P_OK;
	}
	
	for (i=0; i<(sp->p2pSearchFaits->length); i++) {
		VERBOSE(sp,CLIENT," %d:  %s (%ld results)\n", i, 
			sp->p2pSearchFaits->recherches[i].name, sp->p2pSearchFaits->recherches[i].result);
		VERBOSE(sp,GUI,"SEARCH::WORD::%s\n", sp->p2pSearchFaits->recherches[i].name);
	}
	VERBOSE(sp,CLIENT,"\n");
	
	return P2P_OK;
}

/***************************************************************************
Affichage des résultats d'une recherche
***************************************************************************/
int p2p_list_result(server_params *sp, char *req_str) {
	int i,j=0,k;
	long int req=(long int)atoi(req_str);
	
	VERBOSE(sp,GUI,"\n");
	VERBOSE(sp,GUI,"REPLY::CLEAR\n");
	
	if (sp->p2pSearchFaits->length<=req) {
		VERBOSE(sp,CLIENT,"Cette recherche n'existe pas\n\n");
		return P2P_OK;
	}
	
	VERBOSE(sp,CLIENT," %d results for search %ld (%s)\n", sp->p2pSearchFaits->recherches[req].result, 
		sp->p2pSearchFaits->recherches[req].req_id, sp->p2pSearchFaits->recherches[req].name);
	
	for (i=0; i<(sp->p2pResultats->length); i++) {
		if (sp->p2pResultats->resultats[i].req_id==req) {
			VERBOSE(sp,CLIENT," %d: %s --- %s", j, sp->p2pResultats->resultats[i].noeud, 
					sp->p2pResultats->resultats[i].name, sp->p2pResultats->resultats[i].size);
			for (k=20; k>strlen(sp->p2pResultats->resultats[i].name); k--) VERBOSE(sp,CLIENT," ");
			VERBOSE(sp,CLIENT," --- %ld bytes\n", sp->p2pResultats->resultats[i].size);
			j++;
			VERBOSE(sp,GUI,"REPLY::FILE::%s::%ld::%s\n", sp->p2pResultats->resultats[i].name, 
					sp->p2pResultats->resultats[i].size,sp->p2pResultats->resultats[i].noeud);
		}
	}
	VERBOSE(sp,CLIENT,"\n");
	
	return P2P_OK;
}
